package com.lagou.ipersistence.builder.xml;

import com.lagou.ipersistence.builder.BaseBuilder;
import com.lagou.ipersistence.builder.BuilderException;
import com.lagou.ipersistence.io.Resources;
import com.lagou.ipersistence.mapping.Environment;
import com.lagou.ipersistence.parsing.XNode;
import com.lagou.ipersistence.parsing.XPathParser;
import com.lagou.ipersistence.session.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 * @author Yan Jiahong
 * Created on 2020/11/7
 */
public class XMLConfigBuilder extends BaseBuilder {
    /**
     * 一个建造器, 只能使用一次
     */
    private boolean parsed;

    private final XPathParser parser;

    private Document document;

    private XMLMapperBuilder xmlMapperBuilder;

    private String environment;

    public XMLConfigBuilder(InputStream inputStream) {
        this(new XPathParser(inputStream, true, null, new XMLMapperEntityResolver()));
    }

    public XMLConfigBuilder(XPathParser parser) {
        super(new Configuration());
        parsed = false;
        this.parser = parser;
    }

    /**
     * 该方法将核XML文件解析为核心配置类
     *
     * @param in 核心配置文件的InputStream
     */
    public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(in);
        Element rootEle = document.getRootElement();
        List<Element> propEleList = rootEle.selectNodes("//property");
        Properties properties = new Properties();
        for (Element ele : propEleList) {
            String name = ele.attributeValue("name");
            String value = ele.attributeValue("value");
            properties.setProperty(name, value);
        }
        // 使用C3P0连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(properties.getProperty("driverClass"));
        dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        dataSource.setUser(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        // 设置到Configuration容器中
        configuration.setDataSource(dataSource);
        // 解析Mapper.xml列表
        List<Element> mapperEleList = rootEle.selectNodes("//mapper");
        for (Element ele : mapperEleList) {
            String resourcePath = ele.attributeValue("resource");
            InputStream resIn = Resources.getResourceAsStream(resourcePath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration, resIn, resourcePath, null);
            xmlMapperBuilder.parse(resIn);
        }
        return configuration;
    }

    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("一个XMLConfigurationBuilder只能使用一次!");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    private void parseConfiguration(XNode root) {
        // 解析过程出错就抛出错误
        try {
            propertiesElement(root.evalNode("properties"));
            environmentElement(root.evalNode("environments"));
            mappersElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("解析SQL映射器时出错, e: " + e, e);
        }
    }

    private void propertiesElement(XNode node) throws Exception {
        if (node == null) {
            return;
        }
        Properties defaults = node.getChildrenAsProperties();
        String resource = node.getStringAttribute("resource");
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }

    private void environmentElement(XNode node) throws Exception {
        if (node == null) {
            return;
        }
        if (environment == null) {
            environment = node.getStringAttribute("default");
        }
        for (XNode child : node.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                DataSource dataSource = dataSourceElement(child.evalNode("dataSource"));
                Environment.Builder builder = new Environment.Builder(id).dataSource(dataSource);
                configuration.setEnvironment(builder.build());
            }
        }
    }

    private DataSource dataSourceElement(XNode node) throws PropertyVetoException {
        Properties properties = node.getChildrenAsProperties();
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl(properties.getProperty("url"));
        dataSource.setDriverClass(properties.getProperty("driver"));
        dataSource.setUser(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        return dataSource;
    }

    private boolean isSpecifiedEnvironment(String id) {
        if (environment == null) {
            throw new BuilderException("没有指定环境");
        } else if (id == null) {
            throw new BuilderException("每个环境必须用友一个id属性");
        } else {
            return environment.equals(id);
        }
    }

    private void mappersElement(XNode node) {
        if (node == null) {
            return;
        }
        for (XNode child : node.getChildren()) {
            String resource = child.getStringAttribute("resource");
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder builder = new XMLMapperBuilder(configuration, inputStream, resource,
                    configuration.getSqlFragments());
            builder.parse();
        }
    }
}
